package breakthrough.AlphaBetaPlayer;

import java.util.concurrent.atomic.AtomicInteger;

import breakthrough.BreakthroughMove;
import breakthrough.BreakthroughState;
import game.GameState;

/**
 * 
 * @author oberliat
 *
 */
public class IterativeDeepeningAlphaBeta {
	private final int INCREMENT = 1;
	private final int NUM_THREADS = 4;
	private final int INITIAL_DEPTH = 1;
	
	private TimeManager timeManager;
	private CurrentMove bestCurrentMove;
	private AlphaBetaThread threadsRunning[];
	private Object monitor;
	
	public IterativeDeepeningAlphaBeta(Timer timer, int numMoves) {
		this.monitor = new Object();
		this.timeManager = new TimeManager(timer, numMoves, this.monitor);
	}
	
	public BreakthroughMove search(BreakthroughState board) {
		this.bestCurrentMove = new CurrentMove();
		this.threadsRunning = new AlphaBetaThread[NUM_THREADS];
		
		AtomicInteger nextDepth = new AtomicInteger(INITIAL_DEPTH + 5*INCREMENT);
		
		// start the four initial threads
		for (int i = 0; i < NUM_THREADS; i++) {
			this.threadsRunning[i] = new AlphaBetaThread(INITIAL_DEPTH + i*INCREMENT, board, nextDepth);
			this.threadsRunning[i].start();
		}
		if (this.timeManager.getTimeApproximation() > 0) {
			synchronized(this.monitor) {
				// wait until the time manager says its time to return
				try {
					this.monitor.wait();
				}
				catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		
		for (int i = 0; i < NUM_THREADS; i++) {
			CurrentMove tmp = this.threadsRunning[i].getBestCurrentMove();
			if (this.bestCurrentMove.getMove() == null || tmp.getDepth() > this.bestCurrentMove.getDepth()) {
				this.bestCurrentMove = tmp;
			}
			this.threadsRunning[i].setStop(true);
		}
		
		if (this.bestCurrentMove.getMove() != null) {
			System.out.println(this.bestCurrentMove.getDepth());
			
			return this.bestCurrentMove.getMove();
		}
		else {
			return this.getFirstAvailableMove(board);
		}
	}
	
	private BreakthroughMove getFirstAvailableMove(BreakthroughState board) {		
		BreakthroughMove mv = new BreakthroughMove();
		int dir = board.who == GameState.Who.HOME ? +1 : -1;
		
		for (int r=0; r < BreakthroughState.N; r++) {
			for (int c=0; c < BreakthroughState.N; c++) {
				mv.startRow = r;
				mv.startCol = c;
				mv.endingRow = r+dir; mv.endingCol = c;
				if (board.moveOK(mv)) {
					return (BreakthroughMove) mv.clone();
				}
				mv.endingRow = r+dir; mv.endingCol = c+1;
				if (board.moveOK(mv)) {
					return (BreakthroughMove) mv.clone();
				}
				mv.endingRow = r+dir; mv.endingCol = c-1;
				if (board.moveOK(mv)) {
					return (BreakthroughMove) mv.clone();
				}
			}
		}
		
		return mv;
	}
	
	/**
	 * 
	 * @author oberliat
	 *
	 */
	private class CurrentMove {
		private int depth;
		private BreakthroughMove move;
		
		private CurrentMove() {
			this.depth = -1;
			this.move = null;
		}
		
		private synchronized void setCurrentMove(int depth, BreakthroughMove move) {
			if (depth > this.depth) {
				this.depth = depth;
				this.move = move;
			}
		}
		
		private BreakthroughMove getMove() {
			return this.move;
		}
		
		private int getDepth() {
			return this.depth;
		}
	}
	
	/**
	 * 
	 * @author oberliat
	 *
	 */
	private class AlphaBetaThread extends Thread {
		private int depth;
		private BreakthroughState board;
		private CurrentMove bestCurrentMove;
		private AtomicInteger nextDepth;
		private boolean stop;
		private AlphaBeta currentAlphaBeta;
		
		private AlphaBetaThread(int depth, BreakthroughState board, AtomicInteger nextDepth) {
			super();
			this.depth = depth;
			this.board = board;
			this.nextDepth = nextDepth;
			this.bestCurrentMove = new CurrentMove();
			this.stop = false;
			this.currentAlphaBeta = null;
		}
		
		private void setDepth(int depth) {
			this.depth = depth;
		}
		
		private void setStop(boolean stop) {
			this.stop = stop;
			if (stop && this.currentAlphaBeta != null) {
				this.currentAlphaBeta.setStop(true);
			}
		}
		
		@Override
		public void run() {
			this.runAlphaBeta();
		}
		
		synchronized private void repurposeThread() {
			if (!this.stop) {
				this.setDepth(this.nextDepth.get());
				this.nextDepth.set(this.nextDepth.get() + INCREMENT);
				this.runAlphaBeta();
			}
		}
		
		public void runAlphaBeta() {
			this.currentAlphaBeta = new AlphaBeta(this.depth);
			BreakthroughMove move = this.currentAlphaBeta.alphaBeta((BreakthroughState) this.board.clone());
			this.bestCurrentMove.setCurrentMove(this.depth, move);
			this.repurposeThread();
		}
		
		public CurrentMove getBestCurrentMove() {
			return this.bestCurrentMove;
		}
	};
}
